package com.xy.work.template.base.interceptor;
import cn.hutool.core.util.ObjectUtil;
import com.xy.work.template.common.annotation.CacheLock;
import com.xy.work.template.common.constant.CacheKey;
import com.xy.work.template.common.exception.CacheLockException;
import com.xy.work.template.base.utils.spring.SpringUtils;
import com.xy.work.template.common.enums.CommonCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
 * redis 加锁方案
 * @author niuniu
 */
@Slf4j
@Aspect
@Configuration
public class LockMethodInterceptor {
    private final RedissonClient redissonClient= SpringUtils.getBean(RedissonClient.class);
    private final ExpressionParser parser = new SpelExpressionParser();
    @Around("execution(public * *(..)) && @annotation(com.xy.work.template.common.annotation.CacheLock)")
    public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        CacheLock cacheLock = method.getAnnotation(CacheLock.class);
        if (StringUtils.isEmpty(cacheLock.prefix())) {
            throw new CacheLockException(CommonCodeEnum.CACHE_LOCK_PREFIX);
        }
        String userId =  parseUserId(cacheLock);
        String lockKey= CacheKey.LOCK_KEY+cacheLock.prefix()+userId;
        RLock lock = redissonClient.getLock(lockKey);
        if (lock.isLocked()) {
            throw new CacheLockException(CommonCodeEnum.OPS_REPEAT);
        }
        //多个线程进入，会阻塞等待释放锁
        try {
            lock.tryLock(cacheLock.expire(),cacheLock.timeUnit());
            HttpServletRequest httpServletRequest=SpringUtils.getRequest();
            String url;
            if (ObjectUtil.isEmpty(httpServletRequest)) {
                url = "";
            } else {
                assert httpServletRequest != null;
                url = httpServletRequest.getRequestURI();
            }
            log.info(
                    "加锁成功，接口地址：{}、加锁时间：{}、加锁时间单位：{}、进程ID:{}。",
                    url,
                    cacheLock.expire(),
                    cacheLock.timeUnit().name(),
                    Thread.currentThread().getId()
            );
            return pjp.proceed();
        } catch (InterruptedException e) {
            log.error("开启分布式锁失败",e);
            throw new CacheLockException(CommonCodeEnum.SYSTEM_ERROR);
        }catch (Exception e){
            throw e;
        }finally {
            lock.unlock();
            log.info("解锁成功");
        }
    }

    private String parseUserId(CacheLock lock) {
        if(StringUtils.isEmpty(lock.userId())){
            return "";
        }
        Expression expression = parser.parseExpression(lock.userId());
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 通过joinPoint获取被注解方法的形参
        // 给上下文赋值
        context.setVariable("userId", lock.userId());

        return ":"+expression.getValue(context).toString();
    }
}
